CGShading Demo
==============

* :download:`Download example <PyObjCExample-CGShading Demo.zip>`

This is port to python of the "Cocoa CG shading demo" on ADC.

This is currently not a good example of how to program in Python, but does
show that the CGShading API's work from Python.


.. rst-class:: tabber

Sources
-------

.. rst-class:: tabbertab

MyQuartzView.py
...............

.. sourcecode:: python

    """
    Example for using CGShading and CGFunction. This code is directly translated
    from procedural C code and is definitely not good Python style.
    """
    
    import array
    import math
    import random
    import sys
    
    import Cocoa
    import objc
    import Quartz
    from objc import super  # noqa: A004
    
    # Global variables
    frequency = [0.0, 0.0, 0.0, 0.0]
    startPoint = Quartz.CGPoint(0.0, 0.0)
    startRadius = 0.0
    startExtend = False
    endPoint = Quartz.CGPoint(0.0, 0.0)
    endRadius = 0.0
    endExtend = False
    
    shading = None
    function = None
    getFunction = None
    getShading = None
    colorspace = None
    
    DEFAULT_WIDTH = 256
    DEFAULT_HEIGHT = 256
    
    MAX_WIDTH = 1000
    MAX_HEIGHT = 1000
    
    
    def randomPoint():
        return Quartz.CGPoint(random.random(), random.random())
    
    
    def evaluate1(components, input_value, output_value):
        out = []
        for k in range(components - 1):
            out.append(1 + (math.sin(input[0] * frequency[k])) / 2)
        out.append(1)
        return out
    
    
    def getFunction1(colorspace):
        if sys.maxsize > 2**32:
            a_type = "d"
        else:
            a_type = "f"
        domain = array.array(a_type, [-2 * math.pi, 2 * math.pi])
        function_range = array.array(a_type, [0, 1, 0, 1, 0, 1, 0, 1, 0, 1])
        components = 1 + Quartz.CGColorSpaceGetNumberOfComponents(colorspace)
    
        return Quartz.CGFunctionCreate(
            components, 1, domain, components, function_range, evaluate1
        )
    
    
    def evaluate2(components, input_value, output_value):
        c = [0.510, 0.188, 0.910, 0.122]
    
        v = input_value[0]
        out = []
        for k in range(components - 1):
            if v < 0.5:
                out.append(c[k] * 2 * (0.5 - v))
            else:
                out.append(c[k] * 2 * (v - 0.5))
        out.append(1)
        return out
    
    
    def getFunction2(colorspace):
        domain = [0, 1]
        function_range = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
    
        components = 1 + Quartz.CGColorSpaceGetNumberOfComponents(colorspace)
        return Quartz.CGFunctionCreate(
            components, 1, domain, components, function_range, evaluate2
        )
    
    
    def evaluate3(components, input_value, output_value):
        c = [0.3, 0, 0, 0]
    
        out = []
        for k in range(components - 1):
            out.append(c[k] * input_value[0])
        out.append(1)
        return out
    
    
    def getFunction3(colorspace):
        domain = [0, 1]
        function_range = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
    
        components = 1 + Quartz.CGColorSpaceGetNumberOfComponents(colorspace)
        return Quartz.CGFunctionCreate(
            components, 1, domain, components, function_range, evaluate3
        )
    
    
    def getAxialShading(colorspace, function):
        return Quartz.CGShadingCreateAxial(
            colorspace, startPoint, endPoint, function, startExtend, endExtend
        )
    
    
    def getRadialShading(colorspace, function):
        return Quartz.CGShadingCreateRadial(
            colorspace,
            startPoint,
            startRadius,
            endPoint,
            endRadius,
            function,
            startExtend,
            endExtend,
        )
    
    
    class MyQuartzView(Cocoa.NSView):
        def initWithFrame_(self, frameRect):
            global startPoint, startRadius, startExtend
            global endPoint, endRadius, endExtend
    
            super().initWithFrame_(frameRect)
    
            startPoint = Quartz.CGPoint(0, 0)
            startRadius = 0
            startExtend = False
    
            endPoint = Quartz.CGPointMake(0, 0)
            endRadius = 0
            endExtend = False
    
            return self
    
        def drawRect_(self, rect):
            currentContext = Cocoa.NSGraphicsContext.currentContext().graphicsPort()
    
            # Note that at this point the current context CTM is set up such
            # that the context size corresponds to the size of the view
            # i.e. one unit in the context == one pixel
            # Also, the origin is in the bottom left of the view with +y pointing up
            global getFunction
    
            bounds = self.bounds()
    
            angle = 0
            sx = sy = 1
            width = bounds.size.width
            height = bounds.size.height
    
            if getFunction is None:
                self.randomize_(self)
    
            m = Quartz.CGAffineTransformIdentity
            m = Quartz.CGAffineTransformRotate(m, angle)
            m = Quartz.CGAffineTransformScale(m, width, height)
            m = Quartz.CGAffineTransformScale(m, sx, sy)
    
            Quartz.CGContextBeginPage(currentContext, bounds)
    
            Quartz.CGContextTranslateCTM(
                currentContext, bounds.size.width / 2, bounds.size.height / 2
            )
            Quartz.CGContextConcatCTM(currentContext, m)
            Quartz.CGContextTranslateCTM(currentContext, -0.5, -0.5)
    
            Quartz.CGContextSaveGState(currentContext)
    
            Quartz.CGContextClipToRect(currentContext, Quartz.CGRectMake(0, 0, 1, 1))
            Quartz.CGContextSetRGBFillColor(currentContext, 0.7, 0.7, 0.9, 1)
            Quartz.CGContextFillRect(currentContext, Quartz.CGRectMake(0, 0, 1, 1))
    
            Quartz.CGContextDrawShading(currentContext, shading)
    
            Quartz.CGContextRestoreGState(currentContext)
    
            Quartz.CGContextSaveGState(currentContext)
            Quartz.CGContextClipToRect(currentContext, Quartz.CGRectMake(0, 0, 1, 1))
            Quartz.CGContextSetRGBStrokeColor(currentContext, 1, 0, 0, 1)
    
            if getShading == getRadialShading:
                Quartz.CGContextAddArc(
                    currentContext,
                    startPoint.x,
                    startPoint.y,
                    startRadius,
                    math.radians(0),
                    math.radians(360),
                    True,
                )
                Quartz.CGContextClosePath(currentContext)
                Quartz.CGContextMoveToPoint(
                    currentContext, endPoint.x + endRadius, endPoint.y
                )
                Quartz.CGContextAddArc(
                    currentContext,
                    endPoint.x,
                    endPoint.y,
                    endRadius,
                    math.radians(0),
                    math.radians(360),
                    True,
                )
                Quartz.CGContextClosePath(currentContext)
    
            Quartz.CGContextMoveToPoint(currentContext, startPoint.x + 0.01, startPoint.y)
            Quartz.CGContextAddArc(
                currentContext,
                startPoint.x,
                startPoint.y,
                0.01,
                math.radians(0),
                math.radians(360),
                True,
            )
            Quartz.CGContextClosePath(currentContext)
            Quartz.CGContextMoveToPoint(currentContext, startPoint.x, startPoint.y)
            Quartz.CGContextAddLineToPoint(currentContext, endPoint.x, endPoint.y)
    
            ctm = Quartz.CGContextGetCTM(currentContext)
            Quartz.CGContextConcatCTM(currentContext, Quartz.CGAffineTransformInvert(ctm))
            Quartz.CGContextStrokePath(currentContext)
            Quartz.CGContextRestoreGState(currentContext)
    
            Quartz.CGContextSaveGState(currentContext)
            Quartz.CGContextSetGrayStrokeColor(currentContext, 0, 1)
            Quartz.CGContextAddRect(currentContext, Quartz.CGRectMake(0, 0, 1, 1))
            ctm = Quartz.CGContextGetCTM(currentContext)
            Quartz.CGContextConcatCTM(currentContext, Quartz.CGAffineTransformInvert(ctm))
            Quartz.CGContextStrokePath(currentContext)
            Quartz.CGContextRestoreGState(currentContext)
    
            Quartz.CGContextEndPage(currentContext)
    
            Quartz.CGContextFlush(currentContext)
    
        @objc.IBAction
        def randomize_(self, sender):
            global colorspace, getFunction, getShading
            global function, shading
            global startPoint, startRadius, endPoint, endRadius
    
            if colorspace is None:
                colorspace = Quartz.CGColorSpaceCreateDeviceRGB()
    
            for k in range(len(frequency)):
                frequency[k] = random.random()
    
            startPoint = randomPoint()
            startRadius = random.random() / 2
            endPoint = randomPoint()
            endRadius = random.random() / 2
    
            if getFunction == getFunction1:
                getFunction = getFunction2
    
            elif getFunction == getFunction2:
                getFunction = getFunction3
    
            else:
                getFunction = getFunction1
    
            if getShading == getAxialShading:
                getShading = getRadialShading
            else:
                getShading = getAxialShading
    
            function = getFunction(colorspace)
            shading = getShading(colorspace, function)
    
            self.setNeedsDisplay_(True)
    
        @objc.IBAction
        def toggleStartExtend_(self, sender):
            global startExtend, shading
    
            startExtend = not startExtend
            shading = getShading(colorspace, function)
    
            self.setNeedsDisplay_(True)
    
        @objc.IBAction
        def toggleEndExtend_(self, sender):
            global endExtend, shading
    
            endExtend = not endExtend
            shading = getShading(colorspace, function)
    
            self.setNeedsDisplay_(True)

.. rst-class:: tabbertab

main.py
.......

.. sourcecode:: python

    import sys
    
    # AppHelper.runEventLoop()
    import AppKit  # noqa: F401
    import MyQuartzView  # noqa: F401
    
    AppKit.NSApplicationMain(sys.argv)

.. rst-class:: tabbertab

setup.py
........

.. sourcecode:: python

    """
    Script for building the example.
    
    Usage:
        python3 setup.py py2app
    """
    
    from setuptools import setup
    
    setup(
        name="CGShadingDemo",
        app=["main.py"],
        data_files=["English.lproj"],
        setup_requires=["py2app", "pyobjc-framework-Cocoa", "pyobjc-framework-Quartz"],
    )

